package net.jmchilton.spring.collect.spec;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

import net.jmchilton.spring.collect.spec.namespace.Namespace;
import net.jmchilton.spring.collect.spec.namespace.Parameter;
import net.jmchilton.spring.collect.spec.namespace.StaticFactory;

import org.springframework.util.StringUtils;

/**
 * 
 * Upon construction this class loads an XML file containing the
 * namespace declaration of a spec. This object then produces an
 * equivalent namespace object, with two changes.
 * 
 * Imported specs have all of their declarations recursively added
 * to this new namespace, and the imported spec elements are then 
 * removed.
 * 
 * Likewise, class imports are removed and any references to the
 * imported classes are made fully qualified in the namespace.
 * 
 * The resulting namespace object may be obtained via the expanded
 * namespace method.
 * 
 * @author John Chilton
 */
class NamespaceExpander {
  private final Namespace expandedNamespace;
  private final File specFile;
  
  /**
   * Returns the expanded namespace object.
   * 
   * @return The expanded namespace object
   */
  Namespace getExpandedNamespace() {
    return this.expandedNamespace;
  }

  private void expandImportedSpecs() {
    for(final String importedSpecName : this.expandedNamespace.getImportSpec()) {
      expandImportedSpec(importedSpecName);
    }    
  }
  
  private void expandImportedSpec(final String importedSpecName) {
    final NamespaceExpander importedNamespaceExpander = new NamespaceExpander(specFile.getParentFile(), importedSpecName);
    final Namespace importedNamespace = importedNamespaceExpander.getExpandedNamespace();
    for(final StaticFactory staticFactory : importedNamespace.getStaticFactory()) {
      this.expandedNamespace.getStaticFactory().add(staticFactory);
    }
  }
  
  NamespaceExpander(final File specDirectory, final String specName) {
    this.specFile = new File(specDirectory, specName + ".xml");
    this.expandedNamespace = NamespaceXmlUtils.getNamespace(this.specFile);

    // Make defaults explict
    this.makeDefaultsExplict();

    // Expand and remove imported classes
    this.expandClassImports();
    this.expandedNamespace.getImport().clear();

    // Expand and remove imported specs
    this.expandImportedSpecs();
    this.expandedNamespace.getImportSpec().clear();    
    
  }
  
  private void makeDefaultsExplict() {
    for(final StaticFactory factory : expandedNamespace.getStaticFactory()) {
      for(final Parameter parameter : factory.getParameter()) {
        parameter.setMinNum(parameter.getMinNum());
        parameter.setMaxNum(parameter.getMaxNum());
      }
    }
  }

  private void expandClassImports() {
    final Map<String, String> imports = getImports(expandedNamespace);
    for(final StaticFactory factory : expandedNamespace.getStaticFactory()) {
      factory.setClazz(getClass(factory.getClazz(), imports));
      factory.setType(getClass(factory.getType(), imports));
      for(final Parameter parameter : factory.getParameter()) {
        parameter.setType(getClass(parameter.getType(), imports));
      }
    }
  }
    
  private static Map<String, String> getImports(final Namespace namespace) {
    final Map<String, String> importMap = new HashMap<String, String>();
    for(final String imprt : namespace.getImport()) {
      int index = imprt.lastIndexOf('.');
      String qualifiedImport = imprt.substring(index + 1);
      index = qualifiedImport.lastIndexOf('$');
      qualifiedImport = qualifiedImport.substring(index + 1);
      importMap.put(qualifiedImport, imprt);
    }
    return importMap;
  }
  
  /**
   * 
   * @param qualifiedClass
   *          A qualified class name
   * @param imports
   *          Map of imports
   * @return The fully qualified class name.
   */
  private static String getClass(final String qualifiedClass, final Map<String, String> imports) {
    String fullClass;
    String baseClass;
    if(qualifiedClass.contains("[")) {
      baseClass = qualifiedClass.substring(0, qualifiedClass.indexOf("["));
    } else {
      baseClass = qualifiedClass;
    }
    if(imports.containsKey(baseClass)) {
      fullClass = StringUtils.replace(qualifiedClass, baseClass, imports.get(baseClass));
    } else {
      fullClass = qualifiedClass;
    }
    return fullClass;
  }
  

  
}
